#include <stdio.h>

/**
 * 数据类型详解，以及介绍常量与变量。
 */
int main() {

    //1、基本类型：整型：int(整型)、short(短整型)、long(长整型)、long long(长长整型)  字符型：char  实型(浮点型)：float(单精度实型)、double(双精度实型)
    //2、构造类型：数组类型  结构类型：struct  联合类型：union  枚举类型：enum
    //3、指针类型：char *、int *、int **等等

    //常量：在程序运行过程中，其值不能被改变的量。一般就是我们可以目视到的数字，所以又称字面量。
    printf("整型常量：%d、%d、%d\n", 3, -103, 0);
    printf("实型常量：%f、%f、%f\n", 3.14, 0.125, -3.123);
    printf("字符型常量：%c、%c、%c", 'a', '3', '\n');//记住这个\n就代表着换行
    printf("字符串常量：\"这是一串字符串\"\n");

    //变量：在程序运行过程中,其值会被改变的量。我们可以将变量理解成是一个存储数据的容器。变量在使用前必须被定义。
    //语法：type variable_list。type指的是数据类型,variable_list指的是由⼀个或多个标识符名称组成,多个标识符之间用逗号分隔。
    //举例：
    int __i, z9_, kggb;
    float floa, salary;
    //变量一旦被定义就在内存上占用了一块对应大小的空间(这点跟数据类型有关)。但注意:占用这个空间的不是这个变量而是这个变量对应的值。
    //变量命名规则：1、遵循标识符的命名规范 2、不能使用关键字 3、见名知意。建议使用英文单词,不建议使用拼音。上面的举例不要模仿。

    //不同的数据类型占用的内存空间是不一样的。以下是各类型占用空间的展示（注意：实际占用跟你使用的编译器有关，不同编译器造成的结果是不一样的）。
    // sizeof关键字:可以获取某个数据类型所占用的空间
    printf("short类型存储大小: %d字节\n", sizeof(short));
    printf("int类型存储大小: %d字节\n", sizeof(z9_));//直接引入已定义的变量亦可
    printf("long类型存储大小: %d字节\n", sizeof(long));
    printf("long long类型存储大小: %d字节\n", sizeof(long long));

    //虽然占用的空间是定下来的,但是使用占用空间来表示什么范围的数却是灵活的。基于这点,便有了有符号数与无符号数。
    printf("有符号int类型数存储大小: %d字节\n", sizeof(int));
    printf("无符号int类型数存储大小: %d字节\n", sizeof(unsigned int));
    //有符号与无符号占用空间是一样的,但是其可以表示的数却是不一样的
    //有符号：-2147483648 到 2147483647 (-2^31 ~ 2^31-1)  无符号：0 到 4294967295 (0 ~ 2^32-1)
    // 本质上就是对内存空间的利用方式。所以如果说在开发时,预先得知某个数不会为负数时,就可以定义为无符号数,扩大能够存储的最大值的大小。

    //通过常量与常量进行展示
    //顺便解释下printf函数的打印格式问题了：
    // %hd:输出short类型  %d:输出int类型  %ld:输出long类型  %lld:输出long long类型  %hu:输出unsigned short类型
    // %u:输出unsigned int类型  %lu:输出unsigned long类型  %llu:输出unsigned long long类型  %c:输出char类型
    printf("int类型常量：%d\n", 10); //什么都不加默认就是int类型
    printf("long类型常量：%ld、%ld\n", 10l, 10L); //后面加个l或L就代表long类型
    printf("long long类型常量：%lld、%lld\n", 10ll, 10LL); //后面加个ll或LL就代表long long类型
    printf("unsigned int类型常量：%u、%u\n", 10u, 10U); //后面加个u或U就代表unsigned int类型
    unsigned long unsignedLong = 10ul;
    printf("unsigned long类型常量与变量：%lu、%lu\n", 10L, unsignedLong); //后面加个ll或LL就代表unsigned long类型
    //其他类同,就不重复写了
    int intValue = -10;
    //顺带一提,这个打印格式与量本身并不会强依赖。量在定义后,在计算机中就已经以定义时的形式存储好了。这个打印格式,只是代表着printf取出该量后,
    // 将其转换成什么样的格式来打印到控制台的意思。所以运行是可以运行的,但是输出的值是否正确关系就很大了。
    printf("精度丢失情况下的输出：%u\n", intValue);
    // 特殊格式: %x:以十六进制数形式输出整数  %o:以八进制数形式输出整数
    printf("以16/8进制输出：%x、%o、%X\n", -10, -10, -10);
    // 计算机以二进制补码的形式存储数值
    printf("二进制补码转成数: %hd\n", 0B1111111111111111);

    //字符型：字符型变量用于存储单一字符。只占用1个字节。通过使用''来进行引用。也分为char和unsigned char。
    printf("char类型存储大小: %d字节\n", sizeof(char));
    printf("char类型常量：%c\n", 'b');
    printf("char类型常量：%c\n", 'vfa');//字符型只能存储单一字符,所以这样写不符合语法,最终编译后只会存储最后一个字符a(TIPS:该行为取决于编译器，不同编译器结果不一定一样)
    printf("char类型常量：%c\n", 65);//字符在内存中存储时是以这个字符的ASCII编码进行存储的,所以你可以使用数字,此处的65代表字符A。
    //所以如果从计算机内存本质上看,char本质上是一个1字节大小的无符号整型
    //printf("char类型常量：%c\n", '中');//注意,每个中文字符本质上都是2字节大小,所以char类型是没办法定义的。
    //结果竟然是4而非1，这是因为'A'的ASCII码是65，所以被编译器解读成了sizeof(65)，因此变成了求一个int数据类型的大小。但是这个结果取决于编译器，不同编译器结果不一定一样
    // 如果是cpp编译器可能就是1。
    printf("char类型存储大小(但是直接提供了字符): %d字节\n", sizeof('A'));
    //因此这里要再次郑重地进行说明:
    // WARN: sizeof这样的关键字乃至于其他的一些标识符的所产生的效果，往往取决于编译器，所以即便是此处本人所做的demo演示以及效果，都是基于本人此刻
    //       所采用的编译器和操作系统所编写，因此若果无法复刻出完全相同的效果，完全是正常的。

    //ASCII 码大致由以下两部分组成：
    //非打印控制字符: ASCII 表上数字 0-31 分配给了控制字符，用于控制打印机等一些外围设备(TIPS:转义字符的诞生跟打印机息息相关，所以部分转义字符都属于控制字符)。
    //打印字符: 数字 32-126 分配给了能在键盘上找到的字符，当查看或打印文档时就会出现。数字 127 代表 Del 命令。
    //ASCII码的应用:
    printf("大写转小写: %c\n", 'A' + 32);
    printf("小写转大写: %c\n", 'a' - 32);//能做到切换就是因为ASCII码中A是65，a是97
    printf("小写转大写: %c\n", 'a' - ' ');//又因为32代表' '，所以这样也能实现大小写切换
    printf("整数转字符: %c\n", 8 + 48);
    printf("字符转整数: %d\n", '6' - 48);//这里的所谓48代表是ASCII码表中的字符0
    printf("字符转整数: %d\n", '6' - '0');

    /*
        \n 换行(LF) ，将当前位置移到下一行开头
        \r 回车(CR) ，将当前位置移到本行开头
        \t 水平制表(HT) （跳到下一个TAB位置）
        \\ 代表一个反斜线字符""
        \' 代表一个单引号（撇号）字符
        \" 代表一个双引号字符
        \0 字符串结束标记(NUT)，在一些其他语言被称呼为"空字符"
     */
    printf("各转义字符效果测试：%c、%c、%c、%c、%c、%c、%c、%c", 'j', '\r', '\\', '\'', '\"', '\0', '\t', '\n');
    char change = '\n';
    printf("转义字符的\\是用作转义的，不视为一个字符处理，转义字符整体算作一个char类型: %d字节\n", sizeof(change));
    printf("各转义字符对应的ASCII码：%d、%d、%d、%d、%d、%d、%d\n", '\r', '\\', '\'', '\"', '\0', '\t', '\n');

    //实型变量也可以称为浮点型变量，浮点型变量是用来存储小数数值的。
    // 在C语言中，浮点型变量分为两种：单精度浮点数(float)、双精度浮点数(double)。double型变量所表示的浮点数比 float 型变量更精确。
    printf("float类型存储大小: %d字节\n", sizeof(float));
    printf("double类型存储大小: %d字节\n", sizeof(double));
    // 由于浮点型变量是由有限的存储单元组成的，因此只能提供有限的有效数字。在有效位以外的数字将被舍去，这样可能会产生一些误差。
    // 不以f结尾的常量是double类型，以f或F结尾的常量(如3.14f)是float类型。
    // %f:输出float类型  %lf:输出double类型
    //可以采用科学计数法赋值，3.2e3f=3.2*10³ = 3200
    printf("float类型常量：%f、%f、%f、%f、%f\n", 3.14f, 128.101F, 3.2e3f, 3.2E3f, 3.1415926f);
    //结果小数位超过2位时，float就开始出现精度丢失问题了，但是double不会。同时C语言中，输出浮点型默认只输出6位小数（不足六位以0补齐，超过六位按四舍五入截断）。
    //TIPS:在使用printf时，double类型即便不显示使用%lf，也会隐式地进行优化。
    printf("double类型常量：%lf、%f、%lf、%lf、%lf\n", 3.14, 128.101, 3.2e3, 100e-3f, 3.1415926);
    // 特殊的输出格式: %n.mlf——n和m为数字，代表输出多少位(n是包括小数点在内的总位数,m是小数位)，n可以不写  %e或%E或%le或%lE——以指数形式输出浮点型
    printf("浮点型的特殊输出:%.8f、%e、%E、%6.2lf\n", 3.1415926, 0.302f, 3.14f, 3.14);
    /*
        基于此输出的结果聊聊IEEE754标准(3/2根据整除原则剩余1，但是浮点数输出的却是0.000000这点——事实上,这个算式是个障眼法,直接提供1也是这样的结果)。
        我们知道整数1在内存中存储时的二进制为：0000 0000 0000 0000 0000 0000 0000 0001,那么我们在输出结果时，计算机会将整个二进制当成浮点数来处理，
        那么问题这个二进制所表示的浮点数到底是多少呢？
        IEEE754标准：
        32位浮点数格式——31位为符号位(数符S),0正1负。30-23位为阶码E,共8位,以2为底,阶码=阶码真值(指数)+127。
        22-0位为尾数M,共23位,采用隐含尾数最高位1的表示方法,实际尾数24位,尾数真值=1+尾数,表示形式为1.M。
        所以这种格式的非0浮点数真值为:(-1)^s * 2^(阶码-127) * (1+尾数)。V=(-1)^s * 2^(E-127) * (1+M)
        那么在此例中，(-1)^0 * 2^(0B00000000-0B01111111) * 1.00000000000000000000001
               = 1.00000000000000000000001 * 2^(0B10000001)
               //注意此时1后面的部分依旧是2进制，虽然转换很麻烦，但是有2的多次幂存在，所以移位即可。
               = 1.00000000000000000000001 * 2 ^ (-127)
               //向右移127位，结果就是全0。
               = 0
     */
    printf("%f\n", 3 / 2);

    //用一个例子演示一下二进制在底层的运算规则:
    char c = 250;//二进制:1111 1010,由于是个有符号数,所以运算时代表的是-6。
    unsigned char uc;
    char f;
    uc = c + 249;//249二进制:1111 1001
    f = c + 249;
    //TIPS:本质上都是 1111 1010 + 1111 1001 = 1111 0011。输出的值的本质是根据存储的类型而定的。
    printf("运算:%d\n", uc);//因为是无符号数，所以会补0成 0000 0000 1111 0011
    printf("运算:%u\n", uc);//因为是无符号数，所以会补0成 0000 0000 1111 0011
    printf("运算:%d\n", f);//因为是有符号数，1在首位，所以认为这是个负数，所以会补1成 1111 1111 1111 0011。以有符号十进制输出。
    printf("运算:%u\n", f);//因为是有符号数，1在首位，所以认为这是个负数，所以会补1成 1111 1111 1111 0011。以无符号十进制输出。

    //使用整数与浮点数进行运算时(含有浮点数时结果必然是浮点数)
    printf("运算:%d\n", 11 * 3.5);//从这里可以看到C语言对于 整数*浮点数 的运算必然是返回一个浮点数。
    printf("运算:%f\n", 11 * 3.5);//因为运算必然返回的是浮点数，所以计算过程必然不是11*3=33=33.00，而是11*3.5=38.500

    return 0;
}